/* Copyright 2018 Canaan Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "include/sysctl.h"
#include "include/memlayout.h"
#include "include/types.h"

#define SYSCTRL_CLOCK_FREQ_IN0 (26000000UL)

const uint8 get_select_pll2[] = {
    [SYSCTL_SOURCE_IN0] = 0,
    [SYSCTL_SOURCE_PLL0] = 1,
    [SYSCTL_SOURCE_PLL1] = 2,
};

const uint8 get_source_pll2[] = {
    [0] = SYSCTL_SOURCE_IN0,
    [1] = SYSCTL_SOURCE_PLL0,
    [2] = SYSCTL_SOURCE_PLL1,
};

const uint8 get_select_aclk[] = {
    [SYSCTL_SOURCE_IN0] = 0,
    [SYSCTL_SOURCE_PLL0] = 1,
};

const uint8 get_source_aclk[] = {
    [0] = SYSCTL_SOURCE_IN0,
    [1] = SYSCTL_SOURCE_PLL0,
};

volatile sysctl_t *const sysctl = (volatile sysctl_t *)SYSCTL_V;

uint32 sysctl_get_git_id(void) { return sysctl->git_id.git_id; }

uint32 sysctl_get_freq(void) { return sysctl->clk_freq.clk_freq; }

static int sysctl_clock_bus_en(sysctl_clock_t clock, uint8 en) {
  /*
   * The timer is under APB0, to prevent apb0_clk_en1 and apb0_clk_en0
   * on same register, we split it to peripheral and central two
   * registers, to protect CPU close apb0 clock accidentally.
   *
   * The apb0_clk_en0 and apb0_clk_en1 have same function,
   * one of them set, the APB0 clock enable.
   */

  /* The APB clock should carefully disable */
  if (en) {
    switch (clock) {
    /*
     * These peripheral devices are under APB0
     * GPIO, UART1, UART2, UART3, SPI_SLAVE, I2S0, I2S1,
     * I2S2, I2C0, I2C1, I2C2, FPIOA, SHA256, TIMER0,
     * TIMER1, TIMER2
     */
    case SYSCTL_CLOCK_GPIO:
    case SYSCTL_CLOCK_SPI2:
    case SYSCTL_CLOCK_I2S0:
    case SYSCTL_CLOCK_I2S1:
    case SYSCTL_CLOCK_I2S2:
    case SYSCTL_CLOCK_I2C0:
    case SYSCTL_CLOCK_I2C1:
    case SYSCTL_CLOCK_I2C2:
    case SYSCTL_CLOCK_UART1:
    case SYSCTL_CLOCK_UART2:
    case SYSCTL_CLOCK_UART3:
    case SYSCTL_CLOCK_FPIOA:
    case SYSCTL_CLOCK_TIMER0:
    case SYSCTL_CLOCK_TIMER1:
    case SYSCTL_CLOCK_TIMER2:
    case SYSCTL_CLOCK_SHA:
      sysctl->clk_en_cent.apb0_clk_en = en;
      break;

    /*
     * These peripheral devices are under APB1
     * WDT, AES, OTP, DVP, SYSCTL
     */
    case SYSCTL_CLOCK_AES:
    case SYSCTL_CLOCK_WDT0:
    case SYSCTL_CLOCK_WDT1:
    case SYSCTL_CLOCK_OTP:
    case SYSCTL_CLOCK_RTC:
      sysctl->clk_en_cent.apb1_clk_en = en;
      break;

    /*
     * These peripheral devices are under APB2
     * SPI0, SPI1
     */
    case SYSCTL_CLOCK_SPI0:
    case SYSCTL_CLOCK_SPI1:
      sysctl->clk_en_cent.apb2_clk_en = en;
      break;

    default:
      break;
    }
  }

  return 0;
}

static int sysctl_clock_device_en(sysctl_clock_t clock, uint8 en) {
  switch (clock) {
  /*
   * These devices are PLL
   */
  case SYSCTL_CLOCK_PLL0:
    sysctl->pll0.pll_out_en0 = en;
    break;
  case SYSCTL_CLOCK_PLL1:
    sysctl->pll1.pll_out_en1 = en;
    break;
  case SYSCTL_CLOCK_PLL2:
    sysctl->pll2.pll_out_en2 = en;
    break;

  /*
   * These devices are CPU, SRAM, APB bus, ROM, DMA, AI
   */
  case SYSCTL_CLOCK_CPU:
    sysctl->clk_en_cent.cpu_clk_en = en;
    break;
  case SYSCTL_CLOCK_SRAM0:
    sysctl->clk_en_cent.sram0_clk_en = en;
    break;
  case SYSCTL_CLOCK_SRAM1:
    sysctl->clk_en_cent.sram1_clk_en = en;
    break;
  case SYSCTL_CLOCK_APB0:
    sysctl->clk_en_cent.apb0_clk_en = en;
    break;
  case SYSCTL_CLOCK_APB1:
    sysctl->clk_en_cent.apb1_clk_en = en;
    break;
  case SYSCTL_CLOCK_APB2:
    sysctl->clk_en_cent.apb2_clk_en = en;
    break;
  case SYSCTL_CLOCK_ROM:
    sysctl->clk_en_peri.rom_clk_en = en;
    break;
  case SYSCTL_CLOCK_DMA:
    sysctl->clk_en_peri.dma_clk_en = en;
    break;
  case SYSCTL_CLOCK_AI:
    sysctl->clk_en_peri.ai_clk_en = en;
    break;
  case SYSCTL_CLOCK_DVP:
    sysctl->clk_en_peri.dvp_clk_en = en;
    break;
  case SYSCTL_CLOCK_FFT:
    sysctl->clk_en_peri.fft_clk_en = en;
    break;
  case SYSCTL_CLOCK_SPI3:
    sysctl->clk_en_peri.spi3_clk_en = en;
    break;

  /*
   * These peripheral devices are under APB0
   * GPIO, UART1, UART2, UART3, SPI_SLAVE, I2S0, I2S1,
   * I2S2, I2C0, I2C1, I2C2, FPIOA, SHA256, TIMER0,
   * TIMER1, TIMER2
   */
  case SYSCTL_CLOCK_GPIO:
    sysctl->clk_en_peri.gpio_clk_en = en;
    break;
  case SYSCTL_CLOCK_SPI2:
    sysctl->clk_en_peri.spi2_clk_en = en;
    break;
  case SYSCTL_CLOCK_I2S0:
    sysctl->clk_en_peri.i2s0_clk_en = en;
    break;
  case SYSCTL_CLOCK_I2S1:
    sysctl->clk_en_peri.i2s1_clk_en = en;
    break;
  case SYSCTL_CLOCK_I2S2:
    sysctl->clk_en_peri.i2s2_clk_en = en;
    break;
  case SYSCTL_CLOCK_I2C0:
    sysctl->clk_en_peri.i2c0_clk_en = en;
    break;
  case SYSCTL_CLOCK_I2C1:
    sysctl->clk_en_peri.i2c1_clk_en = en;
    break;
  case SYSCTL_CLOCK_I2C2:
    sysctl->clk_en_peri.i2c2_clk_en = en;
    break;
  case SYSCTL_CLOCK_UART1:
    sysctl->clk_en_peri.uart1_clk_en = en;
    break;
  case SYSCTL_CLOCK_UART2:
    sysctl->clk_en_peri.uart2_clk_en = en;
    break;
  case SYSCTL_CLOCK_UART3:
    sysctl->clk_en_peri.uart3_clk_en = en;
    break;
  case SYSCTL_CLOCK_FPIOA:
    sysctl->clk_en_peri.fpioa_clk_en = en;
    break;
  case SYSCTL_CLOCK_TIMER0:
    sysctl->clk_en_peri.timer0_clk_en = en;
    break;
  case SYSCTL_CLOCK_TIMER1:
    sysctl->clk_en_peri.timer1_clk_en = en;
    break;
  case SYSCTL_CLOCK_TIMER2:
    sysctl->clk_en_peri.timer2_clk_en = en;
    break;
  case SYSCTL_CLOCK_SHA:
    sysctl->clk_en_peri.sha_clk_en = en;
    break;

  /*
   * These peripheral devices are under APB1
   * WDT, AES, OTP, DVP, SYSCTL
   */
  case SYSCTL_CLOCK_AES:
    sysctl->clk_en_peri.aes_clk_en = en;
    break;
  case SYSCTL_CLOCK_WDT0:
    sysctl->clk_en_peri.wdt0_clk_en = en;
    break;
  case SYSCTL_CLOCK_WDT1:
    sysctl->clk_en_peri.wdt1_clk_en = en;
    break;
  case SYSCTL_CLOCK_OTP:
    sysctl->clk_en_peri.otp_clk_en = en;
    break;
  case SYSCTL_CLOCK_RTC:
    sysctl->clk_en_peri.rtc_clk_en = en;
    break;

  /*
   * These peripheral devices are under APB2
   * SPI0, SPI1
   */
  case SYSCTL_CLOCK_SPI0:
    sysctl->clk_en_peri.spi0_clk_en = en;
    break;
  case SYSCTL_CLOCK_SPI1:
    sysctl->clk_en_peri.spi1_clk_en = en;
    break;

  default:
    break;
  }

  return 0;
}

int sysctl_clock_enable(sysctl_clock_t clock) {
  if (clock >= SYSCTL_CLOCK_MAX)
    return -1;
  sysctl_clock_bus_en(clock, 1);
  sysctl_clock_device_en(clock, 1);
  return 0;
}

int sysctl_dma_select(sysctl_dma_channel_t channel,
                      sysctl_dma_select_t select) {
  sysctl_dma_sel0_t dma_sel0;
  sysctl_dma_sel1_t dma_sel1;

  /* Read register from bus */
  dma_sel0 = sysctl->dma_sel0;
  dma_sel1 = sysctl->dma_sel1;
  switch (channel) {
  case SYSCTL_DMA_CHANNEL_0:
    dma_sel0.dma_sel0 = select;
    break;

  case SYSCTL_DMA_CHANNEL_1:
    dma_sel0.dma_sel1 = select;
    break;

  case SYSCTL_DMA_CHANNEL_2:
    dma_sel0.dma_sel2 = select;
    break;

  case SYSCTL_DMA_CHANNEL_3:
    dma_sel0.dma_sel3 = select;
    break;

  case SYSCTL_DMA_CHANNEL_4:
    dma_sel0.dma_sel4 = select;
    break;

  case SYSCTL_DMA_CHANNEL_5:
    dma_sel1.dma_sel5 = select;
    break;

  default:
    return -1;
  }

  /* Write register back to bus */
  sysctl->dma_sel0 = dma_sel0;
  sysctl->dma_sel1 = dma_sel1;

  return 0;
}
